markdown-markdown 0.2.1 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +9 -0
- package/README.md +62 -5
- package/dist/cli.d.ts +8 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +449 -22
- package/dist/cli.js.map +1 -1
- package/dist/lib/asset-url.d.ts +3 -0
- package/dist/lib/asset-url.d.ts.map +1 -0
- package/dist/lib/asset-url.js +18 -0
- package/dist/lib/asset-url.js.map +1 -0
- package/dist/lib/document-title.d.ts +4 -0
- package/dist/lib/document-title.d.ts.map +1 -0
- package/dist/lib/document-title.js +18 -0
- package/dist/lib/document-title.js.map +1 -0
- package/dist/lib/figure-placeholder.d.ts +3 -0
- package/dist/lib/figure-placeholder.d.ts.map +1 -0
- package/dist/lib/figure-placeholder.js +16 -0
- package/dist/lib/figure-placeholder.js.map +1 -0
- package/dist/lib/markdown-content.d.ts +2 -0
- package/dist/lib/markdown-content.d.ts.map +1 -0
- package/dist/lib/markdown-content.js +40 -0
- package/dist/lib/markdown-content.js.map +1 -0
- package/dist/lib/markdown-layout.d.ts +7 -0
- package/dist/lib/markdown-layout.d.ts.map +1 -0
- package/dist/lib/markdown-layout.js +7 -0
- package/dist/lib/markdown-layout.js.map +1 -0
- package/dist/lib/review.d.ts +2 -1
- package/dist/lib/review.d.ts.map +1 -1
- package/dist/lib/review.js +8 -3
- package/dist/lib/review.js.map +1 -1
- package/dist/server/asset-path.d.ts +8 -0
- package/dist/server/asset-path.d.ts.map +1 -0
- package/dist/server/asset-path.js +23 -0
- package/dist/server/asset-path.js.map +1 -0
- package/dist/server/host.d.ts.map +1 -1
- package/dist/server/host.js +40 -1
- package/dist/server/host.js.map +1 -1
- package/dist/server/session-store.d.ts +4 -1
- package/dist/server/session-store.d.ts.map +1 -1
- package/dist/server/session-store.js +28 -0
- package/dist/server/session-store.js.map +1 -1
- package/dist/server/tunnel.d.ts +9 -0
- package/dist/server/tunnel.d.ts.map +1 -1
- package/dist/server/tunnel.js +34 -0
- package/dist/server/tunnel.js.map +1 -1
- package/dist/types.d.ts +11 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/web/assets/{_baseUniq-nFqJWqJf.js → _baseUniq-vz0kjUDO.js} +1 -1
- package/dist/web/assets/{arc-Jg9CFoKt.js → arc-DVk1cbBs.js} +1 -1
- package/dist/web/assets/{architectureDiagram-Q4EWVU46-DYR5Lm08.js → architectureDiagram-Q4EWVU46-3VShEZo6.js} +1 -1
- package/dist/web/assets/{blockDiagram-DXYQGD6D-30J7_LoM.js → blockDiagram-DXYQGD6D-D8B1KDUN.js} +1 -1
- package/dist/web/assets/{c4Diagram-AHTNJAMY-BClBvNDA.js → c4Diagram-AHTNJAMY-dEbQ9QJp.js} +1 -1
- package/dist/web/assets/channel-DnkWn0WG.js +1 -0
- package/dist/web/assets/{chunk-4BX2VUAB-Bgzv4fI5.js → chunk-4BX2VUAB-BGurz3w5.js} +1 -1
- package/dist/web/assets/{chunk-4TB4RGXK-BKN4Lng_.js → chunk-4TB4RGXK-CdfqsxwR.js} +1 -1
- package/dist/web/assets/{chunk-55IACEB6-CXq43BG4.js → chunk-55IACEB6-CYiUXTZP.js} +1 -1
- package/dist/web/assets/{chunk-EDXVE4YY-DokDvDhv.js → chunk-EDXVE4YY-DF36_MVU.js} +1 -1
- package/dist/web/assets/{chunk-FMBD7UC4-BNN1454L.js → chunk-FMBD7UC4-TZQnRXUC.js} +1 -1
- package/dist/web/assets/{chunk-OYMX7WX6-CMHPjdY3.js → chunk-OYMX7WX6-BWUVcC2i.js} +1 -1
- package/dist/web/assets/{chunk-QZHKN3VN-D9Xdgwdu.js → chunk-QZHKN3VN-CiGlras9.js} +1 -1
- package/dist/web/assets/{chunk-YZCP3GAM-CNVT9piY.js → chunk-YZCP3GAM-Dd_5umjV.js} +1 -1
- package/dist/web/assets/classDiagram-6PBFFD2Q-Fb0iXXxK.js +1 -0
- package/dist/web/assets/classDiagram-v2-HSJHXN6E-Fb0iXXxK.js +1 -0
- package/dist/web/assets/clone-DeacVf-q.js +1 -0
- package/dist/web/assets/{cose-bilkent-S5V4N54A-Bhn6pAUV.js → cose-bilkent-S5V4N54A-Cy15oRUv.js} +1 -1
- package/dist/web/assets/{dagre-KV5264BT-C4y39Usr.js → dagre-KV5264BT-CBpOuHsZ.js} +1 -1
- package/dist/web/assets/{diagram-5BDNPKRD-rlvQhIY0.js → diagram-5BDNPKRD-BknNx3MZ.js} +1 -1
- package/dist/web/assets/{diagram-G4DWMVQ6-CxlsuhBt.js → diagram-G4DWMVQ6-DuuY7D4J.js} +1 -1
- package/dist/web/assets/{diagram-MMDJMWI5-aO59UQJW.js → diagram-MMDJMWI5-BKK-AoCf.js} +1 -1
- package/dist/web/assets/{diagram-TYMM5635-DboqCGxD.js → diagram-TYMM5635-RIIeffz_.js} +1 -1
- package/dist/web/assets/{erDiagram-SMLLAGMA-BfTNK70Y.js → erDiagram-SMLLAGMA-dcy2jwNQ.js} +1 -1
- package/dist/web/assets/{flowDiagram-DWJPFMVM-D5GqUELM.js → flowDiagram-DWJPFMVM-KGm61tBi.js} +1 -1
- package/dist/web/assets/{ganttDiagram-T4ZO3ILL-0oluOY8-.js → ganttDiagram-T4ZO3ILL-BpjYiOnX.js} +1 -1
- package/dist/web/assets/{gitGraphDiagram-UUTBAWPF-DOvCDomy.js → gitGraphDiagram-UUTBAWPF-COoQs5Py.js} +1 -1
- package/dist/web/assets/{graph-D6tF7y1b.js → graph-slJujSP3.js} +1 -1
- package/dist/web/assets/{index-CLPXS5Pa.js → index-B3sdQzp-.js} +200 -191
- package/dist/web/assets/index-ByBrbBFQ.css +1 -0
- package/dist/web/assets/{infoDiagram-42DDH7IO-DzqzFgrP.js → infoDiagram-42DDH7IO-C_7g68JI.js} +1 -1
- package/dist/web/assets/{ishikawaDiagram-UXIWVN3A-WJU0OO5B.js → ishikawaDiagram-UXIWVN3A-B-SFHLkD.js} +1 -1
- package/dist/web/assets/{journeyDiagram-VCZTEJTY-Bo7zyWBb.js → journeyDiagram-VCZTEJTY-BVgzaLel.js} +1 -1
- package/dist/web/assets/{kanban-definition-6JOO6SKY-DqDWBbmi.js → kanban-definition-6JOO6SKY-CxpGkSmg.js} +1 -1
- package/dist/web/assets/{layout-eCIhMhgM.js → layout-BP2q7n-Y.js} +1 -1
- package/dist/web/assets/{linear-DhP7p6ZM.js → linear-N_yUZqpz.js} +1 -1
- package/dist/web/assets/{min-CmgCLj3n.js → min-dd7tRo1R.js} +1 -1
- package/dist/web/assets/{mindmap-definition-QFDTVHPH-B7bz994e.js → mindmap-definition-QFDTVHPH-DxFGKjLg.js} +1 -1
- package/dist/web/assets/{pieDiagram-DEJITSTG-DlKyyMAM.js → pieDiagram-DEJITSTG-BU3cN1bS.js} +1 -1
- package/dist/web/assets/{quadrantDiagram-34T5L4WZ-B3C_LSAl.js → quadrantDiagram-34T5L4WZ-DNEPjmaG.js} +1 -1
- package/dist/web/assets/{requirementDiagram-MS252O5E-D7q7Uv1-.js → requirementDiagram-MS252O5E-BQgCYotG.js} +1 -1
- package/dist/web/assets/{sankeyDiagram-XADWPNL6-B3AHI7j9.js → sankeyDiagram-XADWPNL6-D7mjdo0e.js} +1 -1
- package/dist/web/assets/{sequenceDiagram-FGHM5R23-C0ym1rK1.js → sequenceDiagram-FGHM5R23-B22Onj0b.js} +1 -1
- package/dist/web/assets/{stateDiagram-FHFEXIEX-_NYky71o.js → stateDiagram-FHFEXIEX-BPCrqHFb.js} +1 -1
- package/dist/web/assets/stateDiagram-v2-QKLJ7IA2-B4qdwuMU.js +1 -0
- package/dist/web/assets/{timeline-definition-GMOUNBTQ-BPub2z2y.js → timeline-definition-GMOUNBTQ-DfaT0XIV.js} +1 -1
- package/dist/web/assets/{vennDiagram-DHZGUBPP-B1ao3Y4V.js → vennDiagram-DHZGUBPP-BbhgT_iA.js} +1 -1
- package/dist/web/assets/{wardley-RL74JXVD-xqk6dIJX.js → wardley-RL74JXVD-C8NzLPTB.js} +1 -1
- package/dist/web/assets/{wardleyDiagram-NUSXRM2D-DHkUBrSw.js → wardleyDiagram-NUSXRM2D-CaIRDXYy.js} +1 -1
- package/dist/web/assets/{xychartDiagram-5P7HB3ND-D62ziF_o.js → xychartDiagram-5P7HB3ND-DxAvUodR.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +4 -2
- package/dist/web/assets/channel-B-PozOMc.js +0 -1
- package/dist/web/assets/classDiagram-6PBFFD2Q-DZU9TAgg.js +0 -1
- package/dist/web/assets/classDiagram-v2-HSJHXN6E-DZU9TAgg.js +0 -1
- package/dist/web/assets/clone-BUwPWatq.js +0 -1
- package/dist/web/assets/index-4xbK2CF5.css +0 -1
- package/dist/web/assets/stateDiagram-v2-QKLJ7IA2-BMjcTPNs.js +0 -1
package/AGENT.md
CHANGED
|
@@ -24,6 +24,15 @@ For agent-facing usage guidance, use the separate [`markdown-markdown-skill`](ht
|
|
|
24
24
|
- Run the test suite with `npm test`.
|
|
25
25
|
- Publish releases through GitHub Actions on `v*` tags; avoid local `npm publish` except for bootstrap or debugging.
|
|
26
26
|
|
|
27
|
+
## Async Review CLI
|
|
28
|
+
|
|
29
|
+
- Prefer the async commands for agent workflows: `markdown-markdown review create`, `review wait`, `review refresh`, and `review close`.
|
|
30
|
+
- `review create` starts the single active review session and opens the browser.
|
|
31
|
+
- `review wait` is the source of truth for user intent. It returns either `finish_review`, `continue_review`, or an `abandoned` result.
|
|
32
|
+
- If `review wait` returns `continue_review`, edit the files in the current workspace context, then run `review refresh` to start the next round.
|
|
33
|
+
- If `review wait` returns `abandoned`, stop and ask the user whether to continue without review or start a fresh review session.
|
|
34
|
+
- The browser UI stays readable during `awaiting_refresh`, but annotation edits are intentionally locked until the next round is ready.
|
|
35
|
+
|
|
27
36
|
## Release Workflow
|
|
28
37
|
|
|
29
38
|
- Treat `main` as the source of truth.
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Local Markdown review and annotation CLI.
|
|
4
4
|
|
|
5
|
-
Point it at a single Markdown file or a directory of Markdown files,
|
|
5
|
+
Point it at a single Markdown file or a directory of Markdown files, review them in the browser, then hand the structured result back to an AI system or another automation step.
|
|
6
6
|
|
|
7
7
|
## Companion Skill
|
|
8
8
|
|
|
@@ -10,6 +10,18 @@ If you want agent instructions for using this CLI, install the separate [`markdo
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
+
```bash
|
|
14
|
+
npm install markdown-markdown
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or run it without a permanent install:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx markdown-markdown review create --browser system ./docs/spec.md
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Development Setup
|
|
24
|
+
|
|
13
25
|
```bash
|
|
14
26
|
npm install
|
|
15
27
|
```
|
|
@@ -34,6 +46,49 @@ The workflow uses GitHub Actions trusted publishing, so no npm token needs to be
|
|
|
34
46
|
|
|
35
47
|
## Use
|
|
36
48
|
|
|
49
|
+
### Async review loop
|
|
50
|
+
|
|
51
|
+
The primary agent-facing flow is asynchronous:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx markdown-markdown review create --browser system ./docs/spec.md
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`review create` starts a single active review session, opens the browser UI, and prints a JSON payload with:
|
|
58
|
+
|
|
59
|
+
- `sessionId`
|
|
60
|
+
- `reviewUrl`
|
|
61
|
+
- `controlUrl`
|
|
62
|
+
- `publicUrl`
|
|
63
|
+
- `round`
|
|
64
|
+
- `phase`
|
|
65
|
+
|
|
66
|
+
Then the agent waits for user action:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx markdown-markdown review wait
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
When the user clicks `Finish review`, `review wait` returns a `finish_review` payload.
|
|
73
|
+
|
|
74
|
+
When the user clicks `Continue reviewing after changes`, `review wait` returns a `continue_review` payload. After the agent edits files, it should request the next round:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx markdown-markdown review refresh
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
When the session is done, clean it up explicitly:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx markdown-markdown review close
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The browser UI keeps the document readable during `continue_review`, but it locks annotation edits until the next round is refreshed. The browser tab title also follows the current file name and review status, which helps when you have multiple tabs open.
|
|
87
|
+
|
|
88
|
+
Markdown image assets can be local relative paths or remote URLs. Local assets are served through the review host so regular Markdown image references keep working in directory mode. Placeholder figure markers such as `` are rendered as centered text labels instead of oversized preview art.
|
|
89
|
+
|
|
90
|
+
### Legacy one-shot mode
|
|
91
|
+
|
|
37
92
|
```bash
|
|
38
93
|
npx markdown-markdown ./docs
|
|
39
94
|
```
|
|
@@ -75,21 +130,23 @@ The CLI will:
|
|
|
75
130
|
- use a public Cloudflare tunnel when `cloudflared` is installed, unless you pass `--no-cloudflare`
|
|
76
131
|
- fail fast with a clear install message when you pass `--cloudflare` but `cloudflared` is missing
|
|
77
132
|
- let you annotate headings, paragraphs, blockquotes, lists, tables, code blocks, images, and horizontal rules
|
|
78
|
-
-
|
|
79
|
-
- print the
|
|
133
|
+
- support two review actions in the browser: `Finish review` and `Continue reviewing after changes`
|
|
134
|
+
- print the structured review result to stdout
|
|
80
135
|
- optionally write the final JSON to `--output`
|
|
81
136
|
- optionally write running/completed/failed status snapshots to `--status-file`
|
|
82
137
|
|
|
83
138
|
## Output
|
|
84
139
|
|
|
85
|
-
The
|
|
140
|
+
The review result JSON includes:
|
|
86
141
|
|
|
87
142
|
- `rootPath`
|
|
88
143
|
- `mode`
|
|
89
144
|
- `files`
|
|
90
145
|
- `annotations`
|
|
91
146
|
- `prompt`
|
|
92
|
-
- `submittedAt`
|
|
147
|
+
- `submittedAt` or `abandonedAt`
|
|
148
|
+
- `action` when the user submits (`finish_review` or `continue_review`)
|
|
149
|
+
- `round`
|
|
93
150
|
|
|
94
151
|
The `prompt` field is a ready-to-send Markdown summary of the requested changes, grouped by file with short anchor text and line ranges so an AI can locate the target block without extra token cost.
|
|
95
152
|
|
package/dist/cli.d.ts
CHANGED
|
@@ -8,6 +8,14 @@ export interface ParsedCliArgs {
|
|
|
8
8
|
statusPath: string | null;
|
|
9
9
|
helpRequested: boolean;
|
|
10
10
|
}
|
|
11
|
+
export type ReviewCommandName = 'create' | 'wait' | 'refresh' | 'close';
|
|
12
|
+
export type CliInvocation = (ParsedCliArgs & {
|
|
13
|
+
kind: 'legacy';
|
|
14
|
+
}) | (ParsedCliArgs & {
|
|
15
|
+
kind: 'review';
|
|
16
|
+
reviewCommand: ReviewCommandName;
|
|
17
|
+
});
|
|
11
18
|
export declare function parseCliArgs(argv: string[]): ParsedCliArgs;
|
|
19
|
+
export declare function parseCliInvocation(argv: string[]): CliInvocation;
|
|
12
20
|
export declare function isDirectExecutionEntry(argv1: string | undefined, moduleUrl: string): boolean;
|
|
13
21
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAuBA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EAIf,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAExE,MAAM,MAAM,aAAa,GACrB,CAAC,aAAa,GAAG;IACf,IAAI,EAAE,QAAQ,CAAC;CAChB,CAAC,GACF,CAAC,aAAa,GAAG;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,aAAa,EAAE,iBAAiB,CAAC;CAClC,CAAC,CAAC;AAqFP,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CAsI1D;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,CA+BhE;AA6oBD,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAS5F"}
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
3
|
-
import { stat } from 'node:fs/promises';
|
|
4
2
|
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
5
6
|
import path from 'node:path';
|
|
7
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
6
8
|
import { fileURLToPath } from 'node:url';
|
|
7
9
|
import { buildCompletedStatus, buildFailedStatus, buildRunningStatus, writeJsonFile } from './lib/lifecycle.js';
|
|
8
10
|
import { discoverMarkdownFiles } from './server/session.js';
|
|
9
11
|
import { launchBrowser } from './server/browser.js';
|
|
10
12
|
import { hasCloudflaredExecutable, launchCloudflaredTunnel } from './server/tunnel.js';
|
|
11
13
|
import { startReviewRuntime } from './server/runtime.js';
|
|
14
|
+
import { startReviewHost } from './server/host.js';
|
|
15
|
+
import { closeSession, getActiveSession, readActiveSessionRuntime, readSessionResult, readSessionRuntime, writeSessionRuntime, } from './server/session-store.js';
|
|
16
|
+
function buildCurrentCliExecArgs() {
|
|
17
|
+
const cliEntry = fileURLToPath(import.meta.url);
|
|
18
|
+
if (cliEntry.endsWith('.ts')) {
|
|
19
|
+
return ['--import', 'tsx', cliEntry];
|
|
20
|
+
}
|
|
21
|
+
return [cliEntry];
|
|
22
|
+
}
|
|
12
23
|
function resolveRealpath(candidate) {
|
|
13
24
|
try {
|
|
14
25
|
return realpathSync(candidate);
|
|
@@ -17,7 +28,7 @@ function resolveRealpath(candidate) {
|
|
|
17
28
|
return path.resolve(candidate);
|
|
18
29
|
}
|
|
19
30
|
}
|
|
20
|
-
function
|
|
31
|
+
function printLegacyUsage() {
|
|
21
32
|
console.error([
|
|
22
33
|
'Usage: markdown-markdown [options] <file-or-directory>',
|
|
23
34
|
'',
|
|
@@ -28,6 +39,29 @@ function printUsage() {
|
|
|
28
39
|
' --no-cloudflare Force localhost only',
|
|
29
40
|
' --output <file> Write the final review JSON to a file',
|
|
30
41
|
' --status-file <file> Write running/completed status updates to a file',
|
|
42
|
+
'',
|
|
43
|
+
'Async review commands:',
|
|
44
|
+
' markdown-markdown review create [options] <file-or-directory>',
|
|
45
|
+
' markdown-markdown review wait',
|
|
46
|
+
' markdown-markdown review refresh',
|
|
47
|
+
' markdown-markdown review close',
|
|
48
|
+
].join('\n'));
|
|
49
|
+
}
|
|
50
|
+
function printReviewUsage() {
|
|
51
|
+
console.error([
|
|
52
|
+
'Usage: markdown-markdown review <create|wait|refresh|close> [options]',
|
|
53
|
+
'',
|
|
54
|
+
'Commands:',
|
|
55
|
+
' create <file-or-directory> Start an async review session and open the browser UI',
|
|
56
|
+
' wait Block until the active review session returns a result',
|
|
57
|
+
' refresh Reload files for the next review round after continue_review',
|
|
58
|
+
' close End the active review session and clean up the daemon',
|
|
59
|
+
'',
|
|
60
|
+
'Options for review create:',
|
|
61
|
+
' --browser app|system Browser launch mode (default: app)',
|
|
62
|
+
' --cloudflare Require a Cloudflare tunnel',
|
|
63
|
+
' --cloudflare=auto|required|off',
|
|
64
|
+
' --no-cloudflare Force localhost only',
|
|
31
65
|
].join('\n'));
|
|
32
66
|
}
|
|
33
67
|
export function parseCliArgs(argv) {
|
|
@@ -94,7 +128,7 @@ export function parseCliArgs(argv) {
|
|
|
94
128
|
outputPath = value;
|
|
95
129
|
continue;
|
|
96
130
|
}
|
|
97
|
-
if (parsingOptions &&
|
|
131
|
+
if (parsingOptions && arg === '--status-file') {
|
|
98
132
|
const nextValue = argv[index + 1];
|
|
99
133
|
if (!nextValue) {
|
|
100
134
|
throw new Error(`Missing value for ${arg}.`);
|
|
@@ -141,6 +175,33 @@ export function parseCliArgs(argv) {
|
|
|
141
175
|
helpRequested: false,
|
|
142
176
|
};
|
|
143
177
|
}
|
|
178
|
+
export function parseCliInvocation(argv) {
|
|
179
|
+
if (argv[0] === 'review') {
|
|
180
|
+
const reviewCommand = argv[1];
|
|
181
|
+
if (!reviewCommand || !['create', 'wait', 'refresh', 'close'].includes(reviewCommand)) {
|
|
182
|
+
throw new Error(`Unknown review command: ${reviewCommand ?? '(missing)'}`);
|
|
183
|
+
}
|
|
184
|
+
const parsed = parseCliArgs(argv.slice(2));
|
|
185
|
+
if (reviewCommand === 'create' && !parsed.targetArg && !parsed.helpRequested) {
|
|
186
|
+
throw new Error('review create requires a file or directory target.');
|
|
187
|
+
}
|
|
188
|
+
if (reviewCommand !== 'create' && parsed.targetArg) {
|
|
189
|
+
throw new Error(`review ${reviewCommand} does not accept a target path.`);
|
|
190
|
+
}
|
|
191
|
+
if (parsed.outputPath || parsed.statusPath) {
|
|
192
|
+
throw new Error('review create/wait/refresh/close do not support --output or --status-file. Use stdout and the async commands instead.');
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
...parsed,
|
|
196
|
+
kind: 'review',
|
|
197
|
+
reviewCommand,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
...parseCliArgs(argv),
|
|
202
|
+
kind: 'legacy',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
144
205
|
function resolveStaticDir() {
|
|
145
206
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
146
207
|
const candidates = [
|
|
@@ -155,6 +216,35 @@ function resolveStaticDir() {
|
|
|
155
216
|
}
|
|
156
217
|
return null;
|
|
157
218
|
}
|
|
219
|
+
async function readJsonFile(filePath) {
|
|
220
|
+
try {
|
|
221
|
+
const content = await readFile(filePath, 'utf8');
|
|
222
|
+
return JSON.parse(content);
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
if (error.code === 'ENOENT') {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function sleep(milliseconds) {
|
|
232
|
+
return new Promise((resolve) => {
|
|
233
|
+
setTimeout(resolve, milliseconds);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function isPidAlive(pid) {
|
|
237
|
+
if (!pid || pid <= 0) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
process.kill(pid, 0);
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
158
248
|
async function writeLifecycleArtifact(filePath, value, label) {
|
|
159
249
|
if (!filePath) {
|
|
160
250
|
return;
|
|
@@ -166,6 +256,75 @@ async function writeLifecycleArtifact(filePath, value, label) {
|
|
|
166
256
|
console.error(`Failed to write ${label} at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
167
257
|
}
|
|
168
258
|
}
|
|
259
|
+
async function resolveTargetContext(targetArg) {
|
|
260
|
+
const targetPath = path.resolve(process.cwd(), targetArg);
|
|
261
|
+
const targetStats = await stat(targetPath).catch(() => null);
|
|
262
|
+
if (!targetStats) {
|
|
263
|
+
throw new Error(`Target path does not exist: ${targetPath}`);
|
|
264
|
+
}
|
|
265
|
+
const mode = targetStats.isFile() ? 'file' : 'directory';
|
|
266
|
+
const files = await discoverMarkdownFiles(targetPath);
|
|
267
|
+
if (files.length === 0) {
|
|
268
|
+
throw new Error(`No markdown files found under: ${targetPath}`);
|
|
269
|
+
}
|
|
270
|
+
return { files, mode, targetPath };
|
|
271
|
+
}
|
|
272
|
+
async function ensureNoConflictingActiveSession() {
|
|
273
|
+
const activeSession = await getActiveSession();
|
|
274
|
+
if (!activeSession) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const runtime = await readActiveSessionRuntime();
|
|
278
|
+
if (runtime && !isPidAlive(runtime.hostPid)) {
|
|
279
|
+
await closeSession(activeSession.sessionId);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const location = runtime?.reviewUrl ?? runtime?.controlUrl;
|
|
283
|
+
throw new Error(`An active review session already exists: ${activeSession.sessionId}${location ? ` (${location})` : ''}. Close it before creating a new one.`);
|
|
284
|
+
}
|
|
285
|
+
async function waitForDaemonReady(readyFile, errorFile, childPid, timeoutMs = 20_000) {
|
|
286
|
+
const deadline = Date.now() + timeoutMs;
|
|
287
|
+
while (Date.now() < deadline) {
|
|
288
|
+
const errorPayload = await readJsonFile(errorFile);
|
|
289
|
+
if (errorPayload?.message) {
|
|
290
|
+
throw new Error(errorPayload.message);
|
|
291
|
+
}
|
|
292
|
+
const readyPayload = await readJsonFile(readyFile);
|
|
293
|
+
if (readyPayload) {
|
|
294
|
+
return readyPayload;
|
|
295
|
+
}
|
|
296
|
+
if (!isPidAlive(childPid)) {
|
|
297
|
+
throw new Error('Review daemon stopped before it reported a ready state.');
|
|
298
|
+
}
|
|
299
|
+
await sleep(100);
|
|
300
|
+
}
|
|
301
|
+
throw new Error('Timed out waiting for the async review daemon to start.');
|
|
302
|
+
}
|
|
303
|
+
function killProcessTree(pid) {
|
|
304
|
+
if (process.platform === 'win32') {
|
|
305
|
+
spawnSync('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' });
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
process.kill(pid, 'SIGTERM');
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
// Ignore kill failures; close flow will fall back to clearing session state.
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
async function requestControl(runtime, endpoint) {
|
|
316
|
+
const response = await fetch(`${runtime.controlUrl}${endpoint}`, {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: {
|
|
319
|
+
'Content-Type': 'application/json',
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
if (!response.ok) {
|
|
323
|
+
const body = await response.text();
|
|
324
|
+
throw new Error(body || `Control request failed: ${response.status}`);
|
|
325
|
+
}
|
|
326
|
+
return (await response.json());
|
|
327
|
+
}
|
|
169
328
|
async function runReviewSession(parsedArgs, targetPath, mode, files, staticDir) {
|
|
170
329
|
const resolvedOutputPath = parsedArgs.outputPath ? path.resolve(process.cwd(), parsedArgs.outputPath) : null;
|
|
171
330
|
const resolvedStatusPath = parsedArgs.statusPath ? path.resolve(process.cwd(), parsedArgs.statusPath) : null;
|
|
@@ -256,39 +415,307 @@ async function runReviewSession(parsedArgs, targetPath, mode, files, staticDir)
|
|
|
256
415
|
await shutdown();
|
|
257
416
|
}
|
|
258
417
|
}
|
|
418
|
+
function parseInternalReviewHostArgs(argv) {
|
|
419
|
+
const values = new Map();
|
|
420
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
421
|
+
const key = argv[index];
|
|
422
|
+
const value = argv[index + 1];
|
|
423
|
+
if (!key?.startsWith('--') || !value) {
|
|
424
|
+
throw new Error(`Malformed internal review host arguments near: ${key ?? '(missing)'}`);
|
|
425
|
+
}
|
|
426
|
+
values.set(key.slice(2), value);
|
|
427
|
+
}
|
|
428
|
+
const mode = values.get('mode');
|
|
429
|
+
const browserMode = values.get('browser');
|
|
430
|
+
const cloudflareMode = values.get('cloudflare');
|
|
431
|
+
if (mode !== 'file' && mode !== 'directory') {
|
|
432
|
+
throw new Error(`Unsupported internal mode: ${mode ?? '(missing)'}`);
|
|
433
|
+
}
|
|
434
|
+
if (browserMode !== 'app' && browserMode !== 'system') {
|
|
435
|
+
throw new Error(`Unsupported internal browser mode: ${browserMode ?? '(missing)'}`);
|
|
436
|
+
}
|
|
437
|
+
if (cloudflareMode !== 'auto' && cloudflareMode !== 'required' && cloudflareMode !== 'off') {
|
|
438
|
+
throw new Error(`Unsupported internal cloudflare mode: ${cloudflareMode ?? '(missing)'}`);
|
|
439
|
+
}
|
|
440
|
+
const sessionId = values.get('session-id');
|
|
441
|
+
const rootPath = values.get('root-path');
|
|
442
|
+
const staticDir = values.get('static-dir');
|
|
443
|
+
const readyFile = values.get('ready-file');
|
|
444
|
+
const errorFile = values.get('error-file');
|
|
445
|
+
if (!sessionId || !rootPath || !staticDir || !readyFile || !errorFile) {
|
|
446
|
+
throw new Error('Missing required internal review host arguments.');
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
sessionId,
|
|
450
|
+
rootPath,
|
|
451
|
+
mode,
|
|
452
|
+
staticDir,
|
|
453
|
+
browserMode,
|
|
454
|
+
cloudflareMode,
|
|
455
|
+
readyFile,
|
|
456
|
+
errorFile,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
async function writeInternalResult(filePath, payload) {
|
|
460
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
461
|
+
await writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
|
|
462
|
+
}
|
|
463
|
+
async function runInternalReviewHost(args) {
|
|
464
|
+
let browser = null;
|
|
465
|
+
let tunnel = null;
|
|
466
|
+
let host = null;
|
|
467
|
+
let shuttingDown = false;
|
|
468
|
+
let shutdownResolver = null;
|
|
469
|
+
const shutdown = async () => {
|
|
470
|
+
if (shuttingDown) {
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
shuttingDown = true;
|
|
474
|
+
await browser?.close();
|
|
475
|
+
await tunnel?.close();
|
|
476
|
+
await host?.close();
|
|
477
|
+
shutdownResolver?.();
|
|
478
|
+
};
|
|
479
|
+
try {
|
|
480
|
+
const files = await discoverMarkdownFiles(args.rootPath);
|
|
481
|
+
host = await startReviewHost({
|
|
482
|
+
files,
|
|
483
|
+
mode: args.mode,
|
|
484
|
+
rootPath: args.rootPath,
|
|
485
|
+
sessionId: args.sessionId,
|
|
486
|
+
staticDir: args.staticDir,
|
|
487
|
+
});
|
|
488
|
+
if (args.cloudflareMode === 'required' && !hasCloudflaredExecutable()) {
|
|
489
|
+
throw new Error('Cloudflare tunnel was requested, but cloudflared is not installed. Install cloudflared or rerun with --no-cloudflare.');
|
|
490
|
+
}
|
|
491
|
+
if (args.cloudflareMode !== 'off') {
|
|
492
|
+
tunnel = await launchCloudflaredTunnel(host.url);
|
|
493
|
+
if (args.cloudflareMode === 'required' && !tunnel) {
|
|
494
|
+
throw new Error('Cloudflare tunnel was requested, but cloudflared could not start. Install cloudflared or rerun with --no-cloudflare.');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
const launchUrl = tunnel?.publicUrl ?? host.url;
|
|
498
|
+
browser =
|
|
499
|
+
args.browserMode === 'system'
|
|
500
|
+
? await launchBrowser(launchUrl, { executableCandidates: [] })
|
|
501
|
+
: await launchBrowser(launchUrl);
|
|
502
|
+
if (browser.process) {
|
|
503
|
+
browser.process.once('exit', () => {
|
|
504
|
+
void host?.notifyBrowserClosed();
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
await writeSessionRuntime(host.sessionId, {
|
|
508
|
+
browserMode: args.browserMode,
|
|
509
|
+
cloudflareMode: args.cloudflareMode,
|
|
510
|
+
controlUrl: host.url,
|
|
511
|
+
hostPid: process.pid,
|
|
512
|
+
publicUrl: tunnel?.publicUrl ?? null,
|
|
513
|
+
reviewUrl: launchUrl,
|
|
514
|
+
startedAt: new Date().toISOString(),
|
|
515
|
+
});
|
|
516
|
+
await writeInternalResult(args.readyFile, {
|
|
517
|
+
browserMode: args.browserMode,
|
|
518
|
+
cloudflareMode: args.cloudflareMode,
|
|
519
|
+
controlUrl: host.url,
|
|
520
|
+
mode: args.mode,
|
|
521
|
+
phase: 'waiting_for_review',
|
|
522
|
+
publicUrl: tunnel?.publicUrl ?? null,
|
|
523
|
+
reviewUrl: launchUrl,
|
|
524
|
+
rootPath: args.rootPath,
|
|
525
|
+
round: 1,
|
|
526
|
+
sessionId: host.sessionId,
|
|
527
|
+
});
|
|
528
|
+
host.result.then(async (result) => {
|
|
529
|
+
if ('action' in result && result.action === 'finish_review') {
|
|
530
|
+
await browser?.close();
|
|
531
|
+
}
|
|
532
|
+
}).catch(() => {
|
|
533
|
+
// Errors are surfaced to control commands and stderr; keep the daemon alive for inspection.
|
|
534
|
+
});
|
|
535
|
+
process.once('SIGINT', () => {
|
|
536
|
+
void shutdown();
|
|
537
|
+
});
|
|
538
|
+
process.once('SIGTERM', () => {
|
|
539
|
+
void shutdown();
|
|
540
|
+
});
|
|
541
|
+
process.once('uncaughtException', (error) => {
|
|
542
|
+
void writeInternalResult(args.errorFile, {
|
|
543
|
+
message: error instanceof Error ? error.message : String(error),
|
|
544
|
+
}).finally(() => shutdown());
|
|
545
|
+
});
|
|
546
|
+
await new Promise((resolve) => {
|
|
547
|
+
shutdownResolver = resolve;
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
await writeInternalResult(args.errorFile, {
|
|
552
|
+
message: error instanceof Error ? error.message : String(error),
|
|
553
|
+
});
|
|
554
|
+
await shutdown();
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function runReviewCreate(parsed) {
|
|
559
|
+
await ensureNoConflictingActiveSession();
|
|
560
|
+
const staticDir = resolveStaticDir();
|
|
561
|
+
if (!staticDir) {
|
|
562
|
+
throw new Error('Build output not found. Run the package build before starting the CLI.');
|
|
563
|
+
}
|
|
564
|
+
const { mode, targetPath } = await resolveTargetContext(parsed.targetArg ?? '');
|
|
565
|
+
const tempDir = await mkdtemp(path.join(tmpdir(), 'markdown-markdown-daemon-'));
|
|
566
|
+
const readyFile = path.join(tempDir, 'ready.json');
|
|
567
|
+
const errorFile = path.join(tempDir, 'error.json');
|
|
568
|
+
const sessionId = randomUUID();
|
|
569
|
+
const child = spawn(process.execPath, [
|
|
570
|
+
...buildCurrentCliExecArgs(),
|
|
571
|
+
'__internal-review-host',
|
|
572
|
+
'--session-id',
|
|
573
|
+
sessionId,
|
|
574
|
+
'--root-path',
|
|
575
|
+
targetPath,
|
|
576
|
+
'--mode',
|
|
577
|
+
mode,
|
|
578
|
+
'--browser',
|
|
579
|
+
parsed.browserMode,
|
|
580
|
+
'--cloudflare',
|
|
581
|
+
parsed.cloudflareMode,
|
|
582
|
+
'--static-dir',
|
|
583
|
+
staticDir,
|
|
584
|
+
'--ready-file',
|
|
585
|
+
readyFile,
|
|
586
|
+
'--error-file',
|
|
587
|
+
errorFile,
|
|
588
|
+
], {
|
|
589
|
+
detached: true,
|
|
590
|
+
stdio: 'ignore',
|
|
591
|
+
});
|
|
592
|
+
child.unref();
|
|
593
|
+
try {
|
|
594
|
+
const ready = await waitForDaemonReady(readyFile, errorFile, child.pid ?? 0);
|
|
595
|
+
process.stdout.write(`${JSON.stringify(ready, null, 2)}\n`);
|
|
596
|
+
}
|
|
597
|
+
finally {
|
|
598
|
+
await rm(tempDir, { force: true, recursive: true });
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async function runReviewWait() {
|
|
602
|
+
const activeSession = await getActiveSession();
|
|
603
|
+
if (!activeSession) {
|
|
604
|
+
throw new Error('No active review session is running.');
|
|
605
|
+
}
|
|
606
|
+
while (true) {
|
|
607
|
+
const result = await readSessionResult(activeSession.sessionId);
|
|
608
|
+
if (result) {
|
|
609
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
const runtime = await readSessionRuntime(activeSession.sessionId);
|
|
613
|
+
if (runtime && !isPidAlive(runtime.hostPid)) {
|
|
614
|
+
throw new Error('The active review daemon stopped before producing a result.');
|
|
615
|
+
}
|
|
616
|
+
await sleep(500);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async function requireActiveRuntime() {
|
|
620
|
+
const activeSession = await getActiveSession();
|
|
621
|
+
if (!activeSession) {
|
|
622
|
+
throw new Error('No active review session is running.');
|
|
623
|
+
}
|
|
624
|
+
const runtime = await readSessionRuntime(activeSession.sessionId);
|
|
625
|
+
if (!runtime) {
|
|
626
|
+
throw new Error(`Runtime metadata for active review session ${activeSession.sessionId} is missing.`);
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
runtime,
|
|
630
|
+
sessionId: activeSession.sessionId,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
async function runReviewRefresh() {
|
|
634
|
+
const { runtime } = await requireActiveRuntime();
|
|
635
|
+
const refreshed = await requestControl(runtime, '/api/control/refresh');
|
|
636
|
+
process.stdout.write(`${JSON.stringify(refreshed, null, 2)}\n`);
|
|
637
|
+
}
|
|
638
|
+
async function runReviewClose() {
|
|
639
|
+
const activeSession = await getActiveSession();
|
|
640
|
+
if (!activeSession) {
|
|
641
|
+
process.stdout.write(`${JSON.stringify({ closed: false, reason: 'no_active_session' }, null, 2)}\n`);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const runtime = await readSessionRuntime(activeSession.sessionId);
|
|
645
|
+
if (runtime) {
|
|
646
|
+
try {
|
|
647
|
+
await requestControl(runtime, '/api/control/close');
|
|
648
|
+
}
|
|
649
|
+
catch {
|
|
650
|
+
// Fall through to the process kill and state cleanup path.
|
|
651
|
+
}
|
|
652
|
+
if (isPidAlive(runtime.hostPid)) {
|
|
653
|
+
killProcessTree(runtime.hostPid);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
657
|
+
const current = await getActiveSession();
|
|
658
|
+
if (!current) {
|
|
659
|
+
process.stdout.write(`${JSON.stringify({ closed: true, sessionId: activeSession.sessionId }, null, 2)}\n`);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
await sleep(100);
|
|
663
|
+
}
|
|
664
|
+
await closeSession(activeSession.sessionId);
|
|
665
|
+
process.stdout.write(`${JSON.stringify({ closed: true, forced: true, sessionId: activeSession.sessionId }, null, 2)}\n`);
|
|
666
|
+
}
|
|
259
667
|
async function main() {
|
|
260
|
-
|
|
668
|
+
if (process.argv[2] === '__internal-review-host') {
|
|
669
|
+
const args = parseInternalReviewHostArgs(process.argv.slice(3));
|
|
670
|
+
await runInternalReviewHost(args);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
let invocation;
|
|
261
674
|
try {
|
|
262
|
-
|
|
675
|
+
invocation = parseCliInvocation(process.argv.slice(2));
|
|
263
676
|
}
|
|
264
677
|
catch (error) {
|
|
265
678
|
console.error(error instanceof Error ? error.message : String(error));
|
|
266
|
-
|
|
679
|
+
printLegacyUsage();
|
|
267
680
|
process.exit(1);
|
|
268
681
|
}
|
|
269
|
-
if (
|
|
270
|
-
|
|
682
|
+
if (invocation.helpRequested) {
|
|
683
|
+
if (invocation.kind === 'review') {
|
|
684
|
+
printReviewUsage();
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
printLegacyUsage();
|
|
688
|
+
}
|
|
271
689
|
process.exit(0);
|
|
272
690
|
}
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
691
|
+
if (invocation.kind === 'review') {
|
|
692
|
+
switch (invocation.reviewCommand) {
|
|
693
|
+
case 'create':
|
|
694
|
+
await runReviewCreate(invocation);
|
|
695
|
+
return;
|
|
696
|
+
case 'wait':
|
|
697
|
+
await runReviewWait();
|
|
698
|
+
return;
|
|
699
|
+
case 'refresh':
|
|
700
|
+
await runReviewRefresh();
|
|
701
|
+
return;
|
|
702
|
+
case 'close':
|
|
703
|
+
await runReviewClose();
|
|
704
|
+
return;
|
|
705
|
+
default:
|
|
706
|
+
throw new Error(`Unsupported review command: ${String(invocation.reviewCommand)}`);
|
|
707
|
+
}
|
|
281
708
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
throw new Error(`No markdown files found under: ${targetPath}`);
|
|
709
|
+
if (!invocation.targetArg) {
|
|
710
|
+
printLegacyUsage();
|
|
711
|
+
process.exit(1);
|
|
286
712
|
}
|
|
713
|
+
const { files, mode, targetPath } = await resolveTargetContext(invocation.targetArg);
|
|
287
714
|
const staticDir = resolveStaticDir();
|
|
288
715
|
if (!staticDir) {
|
|
289
716
|
throw new Error('Build output not found. Run the package build before starting the CLI.');
|
|
290
717
|
}
|
|
291
|
-
await runReviewSession(
|
|
718
|
+
await runReviewSession(invocation, targetPath, mode, files, staticDir);
|
|
292
719
|
}
|
|
293
720
|
export function isDirectExecutionEntry(argv1, moduleUrl) {
|
|
294
721
|
if (!argv1) {
|